﻿package ascript
{
	//#########################
	// Header Includes
	//#########################
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import flash.display.Shape;
	import flash.utils.Dictionary;
	import starling.events.Event;
	import ascript.components.general.*;
	import ascript.components.input.*;
	import ascript.components.physics.*;
	import ascript.components.ai.*;
	import ascript.components.animation.*;
	import ascript.utilities.*;
	
	//#########################
	// Class Declaration
	//#########################
	public class GameLevel extends GameObject
	{
		//=========================
		// Variables
		//=========================	
		// Privates
		protected var levelObjects:Dictionary = new Dictionary();
		
		//=========================
		// Constructors
		//=========================	
		public function GameLevel( tName:String, tData:XML ):void
		{
			// Inheritance
			super( tName, tGame, tData );
		}
					
		override public function onRemovedFromStage( tEvent:Event ):void
		{
			//Inheritance
			super.onRemovedFromStage( tEvent );
			
			// Clear arrays
			clearObjectsLevel( levelObjects, getAttribute( "LevelObjects" ), getAttribute( "LevelObjectsUpdatable" ), getAttribute( "PhysicsEngine" ), true );
		}
		
		//=========================
		// Mutators
		//=========================		
		public function getObject( tWhat:String = "" ):GameObject
		{
			if ( tWhat == "" )
			{
				return levelObjects[ ( objectAttributes[ "LevelObjectRecent" ] ) ];
			}
			return levelObjects[ tWhat ];
		}
	
		public function setObject( tValue:GameObject, tIsAdded:Boolean = true, tIsUpdatable:Boolean = false ):void
		{
			var tempName:String = tValue.getName();
			var tempNameOld:String = tempName;
			
			if ( tIsAdded )
			{
				for ( var i:uint; ; i++ )
				{
					if ( getObject( tempName ) != null )
					{
						tempName = tempNameOld + String( i );
					}
					else
					{
						break;
					}
				}
					
				tValue.setName( tempName );
				objectAttributes[ "LevelObjects" ].push( tempName );
				
				if ( tIsUpdatable )
				{
					objectAttributes[ "LevelObjectsUpdatable" ].push( tempName );
				}
				
				setAttribute( "LevelObjectRecent", tempName );
				levelObjects[ tempName ] = tValue;
				addChild( levelObjects[ tempName ] );
			}
			else
			{
				objectAttributes[ "LevelObjectsUpdatable" ].splice( objectAttributes[ "LevelObjectsUpdatable" ].indexOf( tempName ), 1 );
				removeChild( levelObjects[ tempName ] );
				delete levelObjects[ tempName ];
			}
		}
				
		//=========================
		// Members
		//=========================
		override protected function initObject():void
		{
			// Variables
			setAttribute( "LevelObjectsUpdatable", new Vector.<String> );
			setAttribute( "LevelObjects", new Vector.<String> );
			
			// Load Physics
			loadObject( objectAttributes[ "XMLArchetype" ], "PhysicsEngine" );
			loadObject( objectAttributes[ "XML" ], "PhysicsEngine" );
			
			// Inheritance
			super.initObject();
			
			// Load Objects
			loadObject( objectAttributes[ "XMLArchetype" ], "Objects" );
			loadObject( objectAttributes[ "XML" ], "Objects" );
			
			// Start Game
			game.pauseGame( false );
		}
		
		override protected function loadObject( tXML:XML, tWhat:String ):void
		{
			// Temp Variables
			var tempItem:XML;
			
			// Determine Type
			switch( tWhat )
			{
				case "PhysicsEngine":
					// Set Physics Engine
					if ( tXML.PhysicsEngine != undefined )
					{
						setAttribute( "PhysicsDefaultPixelPerMeter", new uint( ( ( tXML.PhysicsEngine ).PixelPerMeter ).@value ) );
						setAttribute( "PhysicsDefaultGravity", new b2Vec2( ( ( tXML.PhysicsEngine ).Gravity ).@x, ( ( tXML.PhysicsEngine ).Gravity ).@y ) );
						setAttribute( "PhysicsHertz", new Number( 1 / ( ( tXML.PhysicsEngine ).Hertz ).@value ) );
						setAttribute( "PhysicsEngine", new b2World( getAttribute( "PhysicsDefaultGravity" ), true ) );
						getAttribute( "PhysicsEngine" ).SetContactListener( new CollisionListener() );
					}
					break;
				case "Objects":
					// Set Objects
					for each ( tempItem in ( tXML.Objects ).Object )
					{
						setObject( new GameObject( new String( tempItem.@name ), tempItem ), true, new Boolean( new int( tempItem.@updatable ) ) );
					}
					break;
				default:
					super.loadObject( tXML, tWhat );
					break;
			}
		}
				
		override public function updateObject( tLevel:GameLevel = null ):void
		{
			// Inheritance
			super.updateObject( tLevel );
									
			// Update all Updatable Objects
			if ( getAttribute( "LevelObjectsUpdatable" ) != null )
			{
				var tempListLength:uint = getAttribute( "LevelObjectsUpdatable" ).length;
				for ( var i:uint = 0; i < tempListLength; i++ )
				{
					if ( !getObject( getAttribute( "LevelObjectsUpdatable" )[i] ).getAttribute( "IsDeleted" ) )
					{
						getObject( getAttribute( "LevelObjectsUpdatable" )[i] ).updateObject( this );
					}
				}
			}
			
			// Update Physics
			getAttribute( "PhysicsEngine" ).Step( getAttribute( "PhysicsHertz" ), 10, 10 );
			
			// Remove Objects with Delete flag
			clearObjectsLevel( levelObjects, getAttribute( "LevelObjects" ), getAttribute( "LevelObjectsUpdatable" ), getAttribute( "PhysicsEngine" ), false );
		}
		
		override public function pauseObject( tIsPaused:Boolean ):void
		{
			// Inheritance
			super.pauseObject( tIsPaused );
			
			// Toggle Objects
			for ( var tempKey:String in levelObjects )
			{
				levelObjects[ tempKey ].pauseObject( tIsPaused );
			}
		}
	
		public function sortObjectsLevel():void
		{
			getAttribute( "LevelObjects" ).sort( sortObjectNamesLevel );
						
			var tempListLength:uint = getAttribute( "LevelObjects" ).length;
			var tempStart:uint = numChildren - getAttribute( "LevelObjects" ).length;
			for ( var i:uint = 0; i < tempListLength; i++ )
			{
				setChildIndex( levelObjects[ getAttribute( "LevelObjects" )[i] ], ( i + tempStart ) );
			}
		}
		
		protected function sortObjectNamesLevel( tObjectA:String, tObjectB:String ):int
		{
			if ( levelObjects[ tObjectA ].y == levelObjects[ tObjectB ].y )
			{
				return 0;
			}
			else if ( levelObjects[ tObjectA ].y > levelObjects[ tObjectB ].y )
			{
				return 1;
			}
			return -1;
		}
		
		protected function clearObjectsLevel( tObjects:Dictionary, tObjectList:Vector.<String>, tUpdatables:Vector.<String>, tPhysicsEngine:b2World, tIsForced:Boolean ):void
		{
			var tempIndex:int;
			
			for ( var tempKey:String in tObjects )
			{
				if ( tIsForced || tObjects[ tempKey ].getAttribute( "IsDeleted" ) )
				{
					// Delete from Lists
					if ( tUpdatables != null )
					{
						tempIndex = tUpdatables.indexOf( tempKey );
						if ( tempIndex > -1 )
						{
							tUpdatables.splice( tempIndex, 1 );
						}
						tempIndex = tObjectList.indexOf( tempKey )
						{
							tObjectList.splice( tempIndex, 1 );
						}
					}
					
					// Delete from Physics Engine
					if ( tObjects[ tempKey ].getAttribute( "Physics" ) != null )
					{
						tPhysicsEngine.DestroyBody( tObjects[ tempKey ].getAttribute( "Physics" ) );
					}
					
					// Remove
					removeChild( tObjects[ tempKey ] );
					delete tObjects[ tempKey ];
				}
			}
		}
		
		public function createBodyPhysics( tPhysicsEngine:b2World, tPixelRatio:uint, tAttachedObject:GameObject, tLocation:PointSimple, tType:uint, tFixedRotation:Boolean = false, tLinearDamping:Number = 1.0 ):b2Body
		{
			var tempBody:b2Body;
			var tempBodyDef:b2BodyDef = new b2BodyDef();
			
			tempBodyDef.linearDamping = tLinearDamping;
			tempBodyDef.type = tType;
			tempBodyDef.fixedRotation = tFixedRotation;
			tempBodyDef.position = new b2Vec2( ( ( tAttachedObject.x + tLocation.x ) / tPixelRatio ), ( ( tAttachedObject.y + tLocation.y ) / tPixelRatio ) );
			tempBodyDef.userData = tAttachedObject;

			return tPhysicsEngine.CreateBody( tempBodyDef );
		}
		
		public function createFixturePhysics( tShape:b2Shape, tID:String = "", tCategory:int = 0, tDensity:Number = 1.0, tFriction:Number = 1.0, tRestitution:Number = 1.0, tIsSensor:Boolean = false ):b2FixtureDef
		{
			var tempFixtureDef:b2FixtureDef = new b2FixtureDef();
			var tempFilter:b2FilterData = new b2FilterData();
						
			tempFilter.groupIndex = tCategory;
			tempFixtureDef.shape = tShape;
			tempFixtureDef.density = tDensity;
			tempFixtureDef.friction = tFriction;
			tempFixtureDef.isSensor = tIsSensor;
			tempFixtureDef.restitution = tRestitution;
			tempFixtureDef.filter = tempFilter;
			tempFixtureDef.userData = tID;
			
			return tempFixtureDef;
		}
		
		public function createBoxPhysics( tPixelRatio:uint, tLocationLocal:PointSimple, tSize:PointSimple ):b2Shape
		{
			var tempShape:b2PolygonShape = new b2PolygonShape();
			var tempArray:Array = new Array();
			
			// TL, TR, BR, BL
			tempArray[0] = new b2Vec2( ( tLocationLocal.x / tPixelRatio ), ( tLocationLocal.y / tPixelRatio ) );
			tempArray[1] = new b2Vec2( ( ( tLocationLocal.x + tSize.x ) / tPixelRatio ), ( tLocationLocal.y / tPixelRatio ) );
			tempArray[2] = new b2Vec2( ( ( tLocationLocal.x + tSize.x ) / tPixelRatio ), ( ( tLocationLocal.y + tSize.y ) / tPixelRatio ) );
			tempArray[3] = new b2Vec2( ( tLocationLocal.x / tPixelRatio ), ( ( tLocationLocal.y + tSize.y ) / tPixelRatio ) );
						
			tempShape.SetAsArray( tempArray, 4 );
			
			return tempShape;
		}
		
		public function createBoxRotatedPhysics( tPixelRatio:uint, tSize:PointSimple, tRotation:Number, tLocationLocal:PointSimple ):b2Shape
		{
			var tempShape:b2PolygonShape = new b2PolygonShape();
			
			tempShape.SetAsOrientedBox( ( tSize.x / ( tPixelRatio * 2 ) ), ( tSize.y / ( tPixelRatio * 2 ) ), new b2Vec2( ( tLocationLocal.x / tPixelRatio ), ( tLocationLocal.y / tPixelRatio ) ), tRotation );
			
			return tempShape;
		}
		
		public function createCirclePhysics( tPixelRatio:uint, tRadius:Number, tLocationLocal:PointSimple ):b2Shape
		{
			var tempShape:b2CircleShape = new b2CircleShape();
			
			tempShape.SetRadius( ( tRadius / tPixelRatio ) );
			tempShape.SetLocalPosition( new b2Vec2( ( tLocationLocal.x / tPixelRatio ), ( tLocationLocal.y / tPixelRatio ) ) );
			
			return tempShape;
		}
	}
}